package cn.snmp.mutual.org.opennms.protocols.icmp;

import java.util.*;

import cn.snmp.mutual.org.opennms.protocols.exceptions.InsufficientDataException;
import cn.snmp.mutual.org.opennms.protocols.ip.OC16ChecksumProducer;


public class ICMPHeader {
    public static final byte TYPE_ECHO_REPLY = 0;
    public static final byte TYPE_DESTINATION_UNREACHABLE = 3;
    public static final byte CODE_NETWORK_UNREACHABLE = 0;
    public static final byte CODE_HOST_UNREACHABLE = 1;
    public static final byte CODE_PROTOCOL_UNREACHABLE = 2;
    public static final byte CODE_PORT_UNREACHABLE = 3;
    public static final byte CODE_FRAGMENTATION_NEEDED = 4;
    public static final byte CODE_SOURCE_ROUTE_FAILED = 5;
    public static final byte CODE_DESTINATION_NETWORK_UNKNOWN = 6;
    public static final byte CODE_DESTINATION_HOST_UNKNOWN = 7;
    public static final byte CODE_SOURCE_HOST_ISOLATED = 8;
    public static final byte CODE_DESTINATION_NETWORK_ADMIN_PROHIBITED = 9;
    public static final byte CODE_DESTINATION_HOST_ADMIN_PROHIBITED = 10;
    public static final byte CODE_NETWORK_UNREACHABLE_FOR_TOS = 11;
    public static final byte CODE_HOST_UNREACHABLE_FOR_TOS = 12;
    public static final byte CODE_COMMUNICATIONS_ADMIN_PROHIBITIED = 13;
    public static final byte CODE_HOST_PRECEDENCE_VIOLATION = 14;
    public static final byte CODE_PRECEDENCE_CUTOFF_IN_EFFECT = 15;
    public static final byte TYPE_SOURCE_QUENCH = 4;
    public static final byte TYPE_REDIRECT = 5;
    public static final byte CODE_REDIRECT_FOR_NETWORK = 0;
    public static final byte CODE_REDIRECT_FOR_HOST = 1;
    public static final byte CODE_REDIRECT_FOR_TYPE_OF_SERVICE_AND_NETWORK = 2;
    public static final byte CODE_REDIRECT_FOR_TYPE_OF_SERVICE_AND_HOST = 3;
    public static final byte TYPE_ECHO_REQUEST = 8;
    public static final byte TYPE_ROUTER_ADVERTISEMENT = 9;
    public static final byte TYPE_ROUTER_SOLICITATION = 10;
    public static final byte TYPE_TIME_EXCEEDED = 11;
    public static final byte CODE_TTL_EQ_ZERO_IN_TRANSIT = 0;
    public static final byte CODE_TTL_EQ_ZERO_IN_REASSEMBLY = 1;
    public static final byte TYPE_PARAMETER_PROBLEM = 12;
    public static final byte CODE_BAD_IP_HEADER = 0;
    public static final byte CODE_REQUIRED_OPTION_MISSING = 1;
    public static final byte TYPE_TIMESTAMP_REQUEST = 13;
    public static final byte TYPE_TIMESTAMP_REPLY = 14;
    public static final byte TYPE_INFORMATION_REQUEST = 15;
    public static final byte TYPE_INFORMATION_REPLY = 16;
    public static final byte TYPE_ADDRESS_MASK_REQUEST = 17;
    public static final byte TYPE_ADDRESS_MASK_REPLY = 18;
    private byte m_type;
    private byte m_code;
    private short m_checksum;
    private short m_ident;
    private short m_sequence;
    private static short sm_seq;

    public static synchronized short nextSequenceId() {
        if (ICMPHeader.sm_seq == 0) {
            final Date d = new Date();
            final Random r = new Random(d.getTime());
            ICMPHeader.sm_seq = (short) r.nextInt();
        }
        return (short) (++ICMPHeader.sm_seq);
    }

    protected static short byteToShort(final byte b) {
        short s = b;
        if (s < 0) {
            s += 256;
        }
        return s;
    }

    protected static int byteToInt(final byte b) {
        int i = b;
        if (i < 0) {
            i += 256;
        }
        return i;
    }

    public ICMPHeader() {
        this.m_type = 0;
        this.m_code = 0;
        this.m_checksum = 0;
        this.m_ident = 0;
        this.m_sequence = 0;
    }

    public ICMPHeader(final byte type) {
        this();
        this.m_type = type;
    }

    public ICMPHeader(final byte type, final byte code) {
        this(type);
        this.m_code = code;
    }

    public ICMPHeader(final byte type, final byte code, final short checksum, final short identity, final short sequence) {
        this.m_type = type;
        this.m_code = code;
        this.m_checksum = checksum;
        this.m_ident = identity;
        this.m_sequence = sequence;
    }

    public ICMPHeader(final ICMPHeader second) {
        this.m_type = second.m_type;
        this.m_code = second.m_code;
        this.m_checksum = second.m_checksum;
        this.m_sequence = second.m_sequence;
        this.m_ident = second.m_ident;
    }

    public byte getType() {
        return this.m_type;
    }

    protected void setType(final byte type) {
        this.m_type = type;
    }

    public byte getCode() {
        return this.m_code;
    }

    public void setCode(final byte code) {
        this.m_code = code;
    }

    public short getSequenceId() {
        return this.m_sequence;
    }

    public short setNextSequenceId() {
        return this.m_sequence = nextSequenceId();
    }

    public void setSequenceId(final short id) {
        this.m_sequence = id;
    }

    public short getIdentity() {
        return this.m_ident;
    }

    public void setIdentity(final short identity) {
        this.m_ident = identity;
    }

    public short getChecksum() {
        return this.m_checksum;
    }

    protected void setChecksum(final short sum) {
        this.m_checksum = sum;
    }

    public void computeChecksum() {
        final OC16ChecksumProducer summer = new OC16ChecksumProducer();
        this.computeChecksum(summer);
        this.m_checksum = summer.getChecksum();
    }

    protected void computeChecksum(final OC16ChecksumProducer summer) {
        summer.reset();
        summer.add(this.m_type, this.m_code);
        summer.add((short) 0);
        summer.add(this.m_sequence);
        summer.add(this.m_ident);
    }

    protected int storeToBuffer(final byte[] buf, int offset) {
        if (buf.length < offset + 8) {
            throw new ArrayIndexOutOfBoundsException("Array index overflow in buffer");
        }
        buf[offset++] = this.m_type;
        buf[offset++] = this.m_code;
        buf[offset++] = (byte) (this.m_checksum >>> 8 & 0xFF);
        buf[offset++] = (byte) (this.m_checksum & 0xFF);
        buf[offset++] = (byte) (this.m_ident >>> 8 & 0xFF);
        buf[offset++] = (byte) (this.m_ident & 0xFF);
        buf[offset++] = (byte) (this.m_sequence >>> 8 & 0xFF);
        buf[offset++] = (byte) (this.m_sequence & 0xFF);
        return offset;
    }

    protected int loadFromBuffer(final byte[] buf, int offset) {
        if (buf.length < offset + 8) {
            throw new InsufficientDataException("Insufficient data to load ICMP header");
        }
        this.m_type = buf[offset++];
        this.m_code = buf[offset++];
        this.m_checksum = (short) (byteToShort(buf[offset++]) << 8 | byteToShort(buf[offset++]));
        this.m_ident = (short) (byteToShort(buf[offset++]) << 8 | byteToShort(buf[offset++]));
        this.m_sequence = (short) (byteToShort(buf[offset++]) << 8 | byteToShort(buf[offset++]));
        return offset;
    }

    protected static int getNetworkSize() {
        return 8;
    }

    public boolean isEchoReply() {
        return this.m_type == 0;
    }

    public boolean isEchoRequest() {
        return this.m_type == 8;
    }

    public byte[] toBytes() {
        final byte[] b = new byte[8];
        this.storeToBuffer(b, 0);
        return b;
    }

    static {
        ICMPHeader.sm_seq = 0;
    }
}
